home *** CD-ROM | disk | FTP | other *** search
/ NeXT Enterprise Objects Framework 1.1 / NeXT Enterprise Objects Framework 1.1.iso / NextDeveloper / Examples / EnterpriseObjects / QueryByExample / QBEPalette / QBE.m < prev    next >
Encoding:
Text File  |  1995-01-25  |  12.3 KB  |  466 lines

  1. /* QBE.m:
  2.  * You may freely copy, distribute, and reuse the code in this example.
  3.  * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
  4.  * fitness for any particular use.
  5.  *
  6.  * Written by Craig Federighi
  7.  *  
  8.  *
  9.  * Example of a Query By Example object: Connects to an EOController for an
  10.  * easy contruction of qualifiers from data entered into fields of an 
  11.  * existing user interface.
  12.  */
  13.  
  14. #import "QBE.h"
  15. #import "DictionaryDataSource.h"
  16.  
  17. #import <eoaccess/eoaccess.h>
  18. #import <foundation/NSString.h>
  19. #import <foundation/NSArray.h>
  20.  
  21. #import <foundation/NSCharacterSet.h>
  22. #import <foundation/NSScanner.h>
  23.  
  24.  
  25.  
  26. /* A private category to strip white spaces */
  27.  
  28. @interface NSString(_QBEStripWhite) 
  29. - (NSString *)stringStrippingLeadingCharacterSet:(NSCharacterSet *)skipset;
  30. @end
  31.  
  32. @implementation NSString(_QBEStripWhite) 
  33.  
  34. /* Returns autoreleased string produced by generating new string
  35.  * with the leading junk skipped.
  36.  */
  37. - (NSString *)stringStrippingLeadingCharacterSet:(NSCharacterSet *)skipset
  38.   {
  39.     NSRange range;
  40.     
  41.     range = [self rangeOfCharacterFromSet: [skipset invertedSet]];
  42.     return [self substringFromIndex: range.location];
  43. }
  44.  
  45. @end
  46.  
  47. /*  A category on EOAssociation to turn on and off editability of
  48.  *  underlying UI objects
  49.  */
  50. @interface EOAssociation(_QBEEditable)
  51. - (BOOL)isEditable;
  52. - (void)setEditable:(BOOL)yn;
  53. @end
  54.  
  55. @implementation EOAssociation(_QBEEditable)
  56. - (BOOL)isEditable { return NO; }
  57. - (void)setEditable:(BOOL)yn {}
  58. @end
  59.  
  60. @implementation EOControlAssociation(_QBEEditable)
  61. - (BOOL)isEditable {
  62.     Cell * cell;
  63.     if ([[self destination] isKindOfClass:[ActionCell class]])
  64.         cell = [self destination];
  65.     else 
  66.         cell = [[self destination] cell];
  67.     
  68.     return [cell isEditable]; 
  69. }
  70.  
  71. - (void)setEditable:(BOOL)yn {
  72.     Cell * cell;
  73.     if ([[self destination] isKindOfClass:[ActionCell class]])
  74.         cell = [self destination];
  75.     else 
  76.         cell = [[self destination] cell];
  77.  
  78.     [cell setEditable:yn]; 
  79. }
  80. @end
  81.  
  82. @implementation EOColumnAssociation(_QBEEditable)
  83. - (BOOL)isEditable {return [tableView isEditable]; }
  84. - (void)setEditable:(BOOL)yn 
  85.     [tableView setEditable:yn]; 
  86. }
  87. @end
  88.  
  89.  
  90. @implementation EOActionCellAssociation(_QBEEditable)
  91. - (BOOL)isEditable { 
  92.     return [(Cell *)[self destination] isEditable]; 
  93. }
  94.  
  95. - (void)setEditable:(BOOL)yn {
  96.     [(Cell *)[self destination] setEditable:yn]; 
  97. }
  98. @end
  99.  
  100.  
  101. /* Need to tell what sort of associations can participate in QBE
  102.  * editing.  Master-detail associations, for example, should be 
  103.  * deactivated.
  104.  */
  105. @interface EOAssociation(_isQBEEditor)
  106. - (BOOL)isQBEEditor; // Should remain active in QBE editing
  107. @end
  108.  
  109. @implementation EOAssociation(_isQBEEditor)
  110. - (BOOL)isQBEEditor  {return YES;}
  111. @end
  112.  
  113. @implementation EOQualifiedAssociation(_isQBEEditor)
  114. - (BOOL)isQBEEditor  {return NO;}
  115. @end
  116.  
  117. // Make sure an Adaptor is ready to convert values
  118. // (the SybaseAdaptor can't do this until after it's connected)
  119. @interface EOAdaptorChannel (canConvert)
  120. - (BOOL)canConvertValues;
  121. @end
  122.  
  123. @implementation EOAdaptorChannel (canConvert)
  124. - (BOOL)canConvertValues
  125. {
  126.     EOAdaptorChannel *adaptorChannel = nil;
  127.     EOAdaptor *adaptor;
  128.     BOOL retValue = YES;
  129.  
  130.     if (![self isOpen]) {
  131.         adaptor = [[adaptorChannel adaptorContext] adaptor];
  132.         if (![adaptor hasValidConnectionDictionary]){
  133.             if (![adaptor runLoginPanelAndValidateConnectionDictionary])
  134.                 retValue = NO;
  135.         }
  136.  
  137.         if (retValue == YES && ![self openChannel])
  138.             retValue = NO;
  139.     }
  140.     return retValue;
  141. }
  142. @end
  143.  
  144.  
  145.  
  146. /* Actual implementation of QBE object */
  147.  
  148. @implementation QBE
  149.  
  150. - init
  151. {
  152.     return self;
  153. }
  154.  
  155. static NSString *operators[] = {
  156.     @"=",
  157.     @">",
  158.     @"<",
  159.     @"<=",
  160.     @">=",
  161.     NULL
  162. };
  163.  
  164.  
  165. /* Build the qualifier based on the user input */
  166.  
  167. - qualifierForKey:(NSString *)key value:value entity:(EOEntity *)entity
  168. {
  169.     EOAdaptorChannel *adaptorChannel = [[realsource databaseChannel] adaptorChannel];
  170.     EOAdaptor *adaptor = [[adaptorChannel adaptorContext] adaptor];
  171.     NSString **ops;
  172.     NSString *op = nil, *fmt;
  173.     id target = value;
  174.     EOQualifier *qualifier;
  175.     EOAttribute *attr;
  176.     BOOL isStringAttribute;
  177.     
  178.     // Be sure that we can connect to the database before constructing a
  179.     // qualifier.  Without a connection, the adaptor can't
  180.     // formatValue:forAttribute:
  181.     if (![adaptorChannel canConvertValues])
  182.         return nil;
  183.     
  184.     [adaptorChannel setDebugEnabled:YES];
  185.     
  186.     // Check type of attribute so we know whether to quote target
  187.     attr = [entity attributeNamed:key];
  188.     isStringAttribute = (strcmp([attr valueClassName], "NSString") == 0);
  189.     
  190.     // Look for an operator.  If we see one, rip it off and use it    
  191.     if ([value isKindOfClass:[NSString class]]) {
  192.         for(ops=operators; *ops; ops++) {
  193.         if ( [(NSString *)value hasPrefix: *ops] ) {
  194.         op = *ops;
  195.         
  196.         // Need to strip whitespace!
  197.         target = [[(NSString *)value substringFromIndex: [op length]]
  198.                 stringStrippingLeadingCharacterSet: 
  199.                        [NSCharacterSet whitespaceCharacterSet]];
  200.         break;
  201.         }
  202.     }
  203.     }
  204.  
  205.     // compute format string
  206.     if (isStringAttribute && !op) {
  207.     // Wildcard search
  208.     op = @"LIKE";
  209.     target = [NSString stringWithFormat:@"%%%@%%", target];
  210.     } else if (!op) {
  211.     // Default for non string attributes
  212.     op = @"=";
  213.     }
  214.  
  215.     fmt = @"%A %@ %@";
  216.     qualifier = [[EOQualifier alloc] initWithEntity:entity 
  217.         qualifierFormat:fmt, key, op,
  218.         [adaptor formatValue:target forAttribute:attr]];
  219.     return [qualifier autorelease];
  220. }
  221.  
  222.  
  223. - (EOQualifier *)makeQualifierForEo:eo entity:(EOEntity *)entity 
  224.                     conjoin:(BOOL)isConjoin
  225.   // Construct a qualifier for an EO
  226. {
  227.     NSDictionary *valueDict;
  228.     NSArray *attributes;
  229.     NSMutableArray *attrnames;
  230.     int count, i;
  231.     EOQualifier *qualifier, *root = nil;
  232.     
  233.     // get a dictionary of values for the eo
  234.     attributes = [entity attributes];
  235.  
  236.     count = [attributes count];
  237.     attrnames = [NSMutableArray arrayWithCapacity:count];
  238.     
  239.     // We should really only ask for the classAttributes
  240.     for(i=0; i<count; i++)
  241.         [attrnames addObject:
  242.         [(EOAttribute *)[attributes objectAtIndex:i] name]];
  243.  
  244.     valueDict = [eo valuesForKeys:attrnames];   
  245.  
  246.     for(i=0; i<count; i++) {
  247.         NSString *key = [attrnames objectAtIndex:i];
  248.     id value = [valueDict objectForKey:key];
  249.     if( value ) {
  250.         qualifier = [self qualifierForKey:key value:value entity:entity];
  251.         if (qualifier) {
  252.             if (root) {
  253.             if (isConjoin)
  254.                 [root conjoinWithQualifier:qualifier];
  255.             else
  256.                 [root disjoinWithQualifier:qualifier];
  257.         } else 
  258.             root = qualifier;
  259.         }
  260.     }            
  261.     }
  262.     
  263.     return root;
  264. }
  265.  
  266. - makeQualifier:(BOOL)isConjoin
  267. {
  268.     EOEntity *entity;
  269.     NSArray *objects;
  270.     EOQualifier *root = nil;
  271.     int count, i;
  272.     
  273.     // force flush of changes to objects
  274.     [controller saveToObjects];
  275.     objects = [controller allObjects];    
  276.     entity = [realsource entity];
  277.  
  278.     for(count=[objects count],i=0; i < count; i++) {
  279.         id eo = [objects objectAtIndex:i];
  280.     EOQualifier *q = [self makeQualifierForEo:eo entity:entity
  281.                     conjoin:isConjoin];
  282.         if (root && q)
  283.         [root disjoinWithQualifier:q];
  284.     else 
  285.         root = q;
  286.     } 
  287.  
  288.     return root;
  289. }
  290.  
  291. - enterQueryMode:sender
  292. // tweak the controller into QBE state:
  293. //  - no records, buffer edits, save to objects
  294. {
  295.     DictionaryDataSource *dictsource;
  296.     NSArray *associations;
  297.     int i, count;
  298.     EOEntity *entity;
  299.     
  300.     if (realsource) 
  301.         return [self addQuery:nil];
  302.     
  303.     // flush everything we have now
  304.     [controller saveToDataSource];
  305.  
  306.     // swap in tempory datasource
  307.     realsource = [(EODatabaseDataSource *)[controller dataSource] retain];
  308.     dictsource = [[[DictionaryDataSource alloc] init] autorelease];
  309.     [controller setDataSource: dictsource];
  310.     
  311.     // We don't want any detail controllers to be updated to match the
  312.     // QBE entry (since it's meaningless and probably not complete)
  313.     
  314.     // Prepare array to add associations to
  315.     removedAssociations = [[NSMutableArray alloc] init];
  316.     
  317.     // Remove all Qualified Associations
  318.     associations = [controller associations];
  319.     for(count=[associations count], i=0; i < count; i++) {
  320.         EOAssociation *assoc = [associations objectAtIndex: i];
  321.     if (![assoc isQBEEditor]) {
  322.         [removedAssociations addObject: assoc];
  323.         [controller removeAssociation:assoc];
  324.     }
  325.     }
  326.     
  327.     // Remove the next controller (for a fetch)
  328.     nextController = [controller nextController];
  329.     [controller setNextController: nil];
  330.     
  331.     // unset the delegate
  332.     controllersDelegate = [controller delegate];
  333.     [controller setDelegate:nil];
  334.  
  335.     // Should force all "qualifyable" associations (to database attributes) 
  336.     // to editable and all others uneditable.
  337.  
  338.     // Make all remaining associations editable
  339.     // Remember those that weren't so we can restore them.
  340.     entity = [realsource entity];
  341.     wereUneditableAssoc = [[NSMutableArray alloc] init];
  342.     wereEditableAssoc = [[NSMutableArray alloc] init];
  343.     associations = [controller associations];
  344.  
  345.     // Note which are uneditable
  346.     for(count=[associations count], i=0; i < count; i++) {
  347.         EOAssociation *assoc = [associations objectAtIndex: i];
  348.     BOOL isQualifyable = ([entity attributeNamed:[assoc key]] != nil);
  349.     if ( isQualifyable && ![assoc isEditable] ) {
  350.         [wereUneditableAssoc addObject:assoc];
  351.     } else if (!isQualifyable && [assoc isEditable]) {
  352.         [wereUneditableAssoc addObject:assoc];
  353.     }
  354.     }    
  355.     // make them temporily editable
  356.     for(count=[wereUneditableAssoc count], i=0; i < count; i++) {
  357.         EOAssociation *assoc = [wereUneditableAssoc objectAtIndex: i];
  358.     [assoc setEditable:YES];
  359.     }    
  360.     // make them temporily uneditable
  361.     for(count=[wereEditableAssoc count], i=0; i < count; i++) {
  362.         EOAssociation *assoc = [wereEditableAssoc objectAtIndex: i];
  363.     [assoc setEditable:NO];
  364.     }    
  365.  
  366.     return [self addQuery:nil];
  367. }
  368.  
  369. - addQuery:sender
  370. {
  371.     int count;
  372.     if (!realsource) return nil;
  373.  
  374.     count = [[controller allObjects] count];
  375.     
  376.     [controller insertObjectAtIndex:count];
  377.     [controller setSelectionIndexes:
  378.         [NSArray arrayWithObject:[NSNumber numberWithInt:count]]];
  379.  
  380.     return self;
  381. }
  382.  
  383. - exitQueryMode:sender
  384.     // restore old datasource and controller settings
  385. {
  386.     int count, i;
  387.     
  388.     // restore datasource
  389.     [controller setDataSource: realsource]; 
  390.     realsource = nil; // mark that we're out of query mode
  391.     
  392.     // restore removed qualified associations
  393.     for(count=[removedAssociations count], i=0; i<count; i++) {
  394.         EOAssociation *assoc = [removedAssociations objectAtIndex:i];
  395.         [controller addAssociation:assoc];
  396.     }
  397.     [removedAssociations release];
  398.  
  399.     // Reset uneditable associations to back to uneditable again
  400.     for(count=[wereUneditableAssoc count], i=0; i<count; i++) {
  401.         EOAssociation *assoc = [wereUneditableAssoc objectAtIndex:i];
  402.         [assoc setEditable:NO];
  403.     }
  404.     [wereUneditableAssoc release];
  405.     
  406.     // Reset editable associations to back to editable again
  407.     for(count=[wereEditableAssoc count], i=0; i<count; i++) {
  408.         EOAssociation *assoc = [wereEditableAssoc objectAtIndex:i];
  409.         [assoc setEditable:YES];
  410.     }
  411.     [wereEditableAssoc release];
  412.     
  413.     
  414.     // restore next controller
  415.     [controller setNextController:nextController];
  416.     
  417.     // restore the delegate
  418.     [controller setDelegate:controllersDelegate];
  419.     
  420.     [controller fetch];    
  421.  
  422.     return self;
  423. }
  424.  
  425. - actionOutsideQueryMode
  426. {
  427.     // We're not in query mode and they pushed a query button.
  428.     // Reset the qualifier and do a basic fetch
  429.     EODatabaseDataSource *source = (EODatabaseDataSource *)[controller dataSource];
  430.     [source setAuxiliaryQualifier:nil];
  431.     [controller fetch];
  432.     return self;
  433. }
  434.  
  435. - applyQualifier:sender
  436. // set qualifier to AND query for current attr settings, 
  437. // exit query mode and fetch
  438. {
  439.     if (!realsource) 
  440.         return [self actionOutsideQueryMode];
  441.     
  442.     [realsource setAuxiliaryQualifier:[self makeQualifier:YES]];  // conjoin
  443.     return [self exitQueryMode:self];
  444. }
  445.  
  446. - applyDisjointQualifier:sender
  447. // set qualifier to AND query for current attr settings, 
  448. // exit query mode and fetch
  449. {
  450.     if (!realsource) 
  451.         return [self actionOutsideQueryMode];
  452.     
  453.     [realsource setAuxiliaryQualifier:[self makeQualifier:NO]];  // Disjoin
  454.     return [self exitQueryMode:self];
  455. }
  456.  
  457. - toggleQueryFetch:sender
  458. {
  459.     return (realsource) ? [self applyQualifier:sender] 
  460.                             : [self enterQueryMode:sender];
  461. }
  462.  
  463. @end
  464.  
  465.